home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / cmds / update / update.c < prev    next >
C/C++ Source or Header  |  1992-10-30  |  35KB  |  1,393 lines

  1. /* 
  2.  * update.c --
  3.  *
  4.  *    A smart copy program that preserves date stamps and only
  5.  *    copies if files are out-of-date.
  6.  *
  7.  * Copyright 1988 Regents of the University of California
  8.  * Permission to use, copy, modify, and distribute this
  9.  * software and its documentation for any purpose and without
  10.  * fee is hereby granted, provided that the above copyright
  11.  * notice appear in all copies.  The University of California
  12.  * makes no representations about the suitability of this
  13.  * software for any purpose.  It is provided "as is" without
  14.  * express or implied warranty.
  15.  */
  16.  
  17. #ifndef lint
  18. static char rcsid[] = "$Header: /sprite/src/cmds/update/RCS/update.c,v 1.33 92/10/30 14:01:08 mottsmth Exp $ SPRITE (Berkeley)";
  19. #endif /* not lint */
  20.  
  21. #include <a.out.h>
  22. #include <errno.h>
  23. #include <stdio.h>
  24. #include <stdlib.h>
  25. #include <string.h>
  26. #include <sys/types.h>
  27. #include <sys/dir.h>
  28. #include <sys/file.h>
  29. #include <sys/param.h>
  30. #include <sys/stat.h>
  31. #include <sys/time.h>
  32. #include <limits.h>
  33. #ifdef sprite
  34. #include <fs.h>
  35. #endif
  36. #include <grp.h>
  37. #include <pwd.h>
  38. #include "regexp.h"
  39. #include "option.h"
  40.  
  41. #ifndef HASSTRERROR
  42. char *strerror();
  43. #endif
  44.  
  45. #if (defined(sunos) || defined(OSF1))
  46. #define DirObject struct dirent
  47. #else
  48. #define DirObject struct direct
  49. #endif
  50.  
  51. /*
  52.  * Library imports:
  53.  */
  54.  
  55. extern long lseek();
  56.  
  57. /*
  58.  * Variables and tables used to parse and identify switch values:
  59.  */
  60.  
  61. static int strip = 0;
  62. static int move = 0;
  63. static char *backupDir = (char *)0;
  64. static int backupAge = 14;
  65. static char *modeString = (char *)0;
  66. static int newMode = -1;
  67. static char *ownerName = (char *)0;
  68. static int newOwner = -1;
  69. static int preserveOwnership = 0;
  70. static char *groupName = (char *)0;
  71. static int newGroup = -1;
  72. static int force = 0;
  73. static int noLinks = 0;
  74. static int copyLinks = 1;
  75. static int setTimes = 1;
  76. static int quietMode = 0;
  77. static int verifyMode = 0;
  78. static int niceMode = 0;
  79. static int PruneOpt();
  80. static int ignoreLinks = 0;
  81.  
  82. static Option optionArray[] = {
  83.     {OPT_STRING, "b", (char *) &backupDir,
  84.         "Next argument contains name of backup directory"},
  85.     {OPT_INT, "B", (char *) &backupAge,
  86.         "Next argument contains age (in days) needed to cause backup to overwrite older backup"},
  87.     {OPT_TRUE, "f", (char *) &force,
  88.         "Force: always update, regardless of time stamps"},
  89.     {OPT_STRING, "g", (char *) &groupName,
  90.         "Next argument contains name of group for destination file(s)"},
  91.     {OPT_FALSE, "l", (char *) ©Links,
  92.         "Copy files referenced by symbolic links\n\t\tDefault: copy symbolic links as symbolic links"},
  93.     {OPT_STRING, "m", (char *) &modeString,
  94.         "Next argument contains new protection bits for file(s)"},
  95.     {OPT_TRUE, "M", (char *) &move, "Move instead of copy"},
  96.     {OPT_TRUE, "n", (char *) &niceMode, "Be nice about potential errors"},
  97.     {OPT_STRING, "o", (char *) &ownerName,
  98.         "Next argument contains name of owner for destination file(s)"},
  99.     {OPT_TRUE, "O", (char *) &preserveOwnership,
  100.         "Preserve ownership of files"},
  101.     {OPT_TRUE, "q", (char *) &quietMode,
  102.         "Quiet mode:  only print error messages"},
  103.     {OPT_TRUE, "s", (char *) &strip, "Strip destination (not supported on ds3100/hpux"},
  104.     {OPT_FALSE, "t", (char *) &setTimes, "Use current time for file access times\n\t\tDefault: set target times to match source"},
  105.     {OPT_TRUE, "v", (char *) &verifyMode,
  106.         "Verify mode:  print messages, but don't modify anything"},
  107.     {OPT_FUNC, "p", (char *) PruneOpt,
  108.         "Prune sub-trees defined by given regular expression"}, 
  109.     {OPT_TRUE, "i", (char *) &ignoreLinks,
  110.         "Ignore symbolic links completely"}, 
  111. };
  112.  
  113. /*
  114.  * Miscellaneous global variables:
  115.  */
  116.  
  117. static int realUid;        /* The user id of the caller (which is not
  118.                  * our effective user id, since we're running
  119.                  * setuid. */
  120.  
  121. #define MAX_PRUNE    25
  122. static int prune = 0;        /* Number of regular expressions to prune. */
  123. static struct {
  124.     regexp    *exp;        /* compiled expression */
  125.     char    *expString;    /* expression string */
  126. } pruneArray[MAX_PRUNE]; /* Regular expressions to prune. */
  127.  
  128. #ifdef sprite
  129. #define MAKELINK(x,y,z) Fs_SymLink((x),(y),((z)==S_IFRLNK))
  130. #define MAKEMSG(x) Stat_GetMsg(x)
  131. #define GETATTRS(x,y) Fs_GetAttributes((x),FALSE,(y))
  132. #else
  133. typedef int ReturnStatus;
  134. #define SUCCESS 0
  135. #define MAKELINK(x,y,z) symlink((x),(y))
  136. #define MAKEMSG(x) strerror(x)
  137. #define GETATTRS(x,y) stat((x),(y))
  138. #endif
  139. /*
  140.  * Forward references to procedures declared later in this file:
  141.  */
  142.  
  143. static int Update();
  144. static int UpdateDir();
  145. static int Copy();
  146. static void CheckGroup();
  147. static void PrintUsageAndExit();
  148. static int SetAttributes();
  149. static int CreateDirectory();
  150.  
  151.  
  152. /*
  153.  *----------------------------------------------------------------------
  154.  *
  155.  * main --
  156.  *
  157.  *    Main program for "update".
  158.  *
  159.  * Results:
  160.  *    None.
  161.  *
  162.  * Side effects:
  163.  *    Files get copied, etc.
  164.  *
  165.  *----------------------------------------------------------------------
  166.  */
  167. void
  168. main(argc, argv)
  169.     int argc;
  170.     char *argv[];
  171. {
  172.     char *destFile;
  173.     char *term;
  174.     struct stat srcAttr, destAttr;
  175.     register int argIndex;
  176.     int numErrors = 0;
  177.     char *userName;
  178.  
  179.     /*
  180.      * Suck up command line options and check protection.
  181.      */
  182.  
  183.     argc = Opt_Parse(argc, argv, optionArray, Opt_Number(optionArray),
  184.         OPT_ALLOW_CLUSTERING);
  185. #if (defined(ds3100) || defined(__mips) || defined(hpux))
  186.     if (strip) {
  187.     strip = 0;
  188.     }
  189. #endif
  190.     realUid = getuid();
  191.     if (preserveOwnership ||
  192.     (ownerName != (char *) 0) || (groupName != (char *) 0)) {
  193.     register struct passwd *pwPtr;
  194.  
  195.     pwPtr = getpwuid(realUid);
  196.     if (pwPtr == (struct passwd *) 0) {
  197.         fprintf(stderr,
  198.             "Couldn't find password entry for user %d.\n",
  199.             realUid);
  200.         exit(1);
  201.     }
  202.     userName = malloc((unsigned) (strlen(pwPtr->pw_name) + 1));
  203.     strcpy(userName, pwPtr->pw_name);
  204.     setpwent();
  205.     }
  206.     if (preserveOwnership) {
  207.     if (ownerName != (char *)0) {
  208.         fprintf(stderr, "Ignoring -o option in favor of -O\n");
  209.         ownerName = (char *)0;
  210.     }
  211.     if (groupName != (char *)0) {
  212.         fprintf(stderr, "Ignoring -g option in favor of -O\n");
  213.         groupName = (char *)0;
  214.     }
  215.     /*
  216.      * Don't allow ownership preservation unless:
  217.      *    1. Caller is root -or-
  218.      *    2. Caller is in the wheel group.
  219.      */
  220.     if (realUid != 0) {
  221.         CheckGroup("wheel", userName);
  222.         realUid = 0;
  223.     }
  224.     }
  225.  
  226.     if (ownerName != (char *)0) {
  227.     register struct passwd *pwPtr;
  228.     pwPtr = getpwnam(ownerName);
  229.     if (pwPtr == (struct passwd *)0) {
  230.         fprintf(stderr, "Unknown user \"%s\".\n", ownerName);
  231.         exit(1);
  232.     }
  233.     newOwner = pwPtr->pw_uid;
  234.  
  235.     /*
  236.      * Don't allow a change of owner unless one of three things is true:
  237.      *     1. Caller is root.
  238.      *     2. Target uid is root, and the caller is in the "wheel" group.
  239.      *     3. There's a group name by the same name as the target uid,
  240.      *      and the caller is in the group.
  241.      */
  242.  
  243.     if ((realUid != 0) && (realUid != newOwner)) {
  244.         register struct group *grPtr;
  245.         char *groupName;
  246.         int i;
  247.  
  248.         if (newOwner == 0) {
  249.         groupName = "wheel";
  250.         } else {
  251.         groupName = ownerName;
  252.         }
  253.         CheckGroup(groupName, userName);
  254.     }
  255.     realUid = newOwner;
  256.     }
  257.     if (setuid(realUid) != 0) {
  258.     fprintf(stderr, "Couldn't change user id: %s\n", strerror(errno));
  259.     exit(1);
  260.     }
  261.  
  262.     if (groupName != (char *) 0) {
  263.     register struct group *grPtr;
  264.     grPtr = getgrnam(groupName);
  265.     if (grPtr == (struct group *)0) {
  266.         fprintf(stderr, "Unknown group \"%s\".\n", groupName);
  267.         exit(1);
  268.     }
  269.     newGroup = grPtr->gr_gid;
  270.  
  271.     /*
  272.      * Don't allow a change of group unless either
  273.      *     1. Caller is root.
  274.      *     2. Caller is in the group being changed to.
  275.      */
  276.  
  277.     if (realUid != 0) {
  278.         int i;
  279.  
  280.         for (i = 0; ; i++) {
  281.         if (grPtr->gr_mem[i] == NULL) {
  282.             fprintf(stderr,
  283.                 "Sorry, but you're not in the \"%s\" group.\n",
  284.                 groupName);
  285.             exit(1);
  286.         }
  287.         if (strcmp(grPtr->gr_mem[i], userName) == 0) {
  288.             break;
  289.         }
  290.         }
  291.     }
  292.     endgrent();
  293.     }
  294.     if (modeString != (char *)0) {
  295.     newMode = strtoul(modeString, &term, 8);
  296.     if ((term == modeString) || (*term != 0)) {
  297.         fprintf(stderr, "Bad mode value \"%s\": should be octal integer\n",
  298.             modeString);
  299.         exit(1);
  300.     }
  301.     }
  302.  
  303.     /*
  304.      * Convert backupAge into the date we need in seconds, instead of days
  305.      * prior to the current time.
  306.      */
  307.     if (backupDir && backupAge > 0) {
  308.     backupAge = time(0) - backupAge * 60 * 60 * 24;
  309.     }
  310.     /*
  311.      * Check for a reasonable number of arguments (normally at least 2
  312.      * in addition to the command name).
  313.      */
  314.  
  315.     if (argc < 3) {
  316.     if (argc == 2) {
  317.         destFile = argv[1];
  318.         if ((stat(destFile, &destAttr) == 0)
  319.             && ((destAttr.st_mode & S_IFMT) == S_IFDIR)) {
  320.         /*
  321.          * Exit cleanly if there is a target directory but no
  322.          * sources.  This situation arises occasionally in makefiles.
  323.          */
  324.         fprintf(stderr, "No sources for \"%s\".\n", destFile);
  325.         exit(0);
  326.         }
  327.     }
  328.     PrintUsageAndExit(argv);
  329.     }
  330.  
  331.     /*
  332.      * Determine the initial case.  There are two:
  333.      *     % update {src1 ... srcN} destDir
  334.      *  % update src dest
  335.      *
  336.      * The tricky thing is how to decide when to treat the destination
  337.      * as a directory (and thus put things INSIDE it) and when to treat
  338.      * it as a file (and thus REPLACE it).  We always work in REPLACE
  339.      * mode unless the destination is a directory and either a) there's
  340.      * more than one source or b) the single source isn't a directory.
  341.      */
  342.  
  343.     destFile = argv[argc-1];
  344.     if (stat(destFile, &destAttr) != 0) {
  345.     if (errno == ENOENT) {
  346.         if (argc == 3) {
  347.         /*
  348.          * update src dest - src can be file or directory
  349.          */
  350.         numErrors = Update(argv[1], argv[2]);
  351.         exit(numErrors);
  352.         }
  353.  
  354.         /*
  355.          * Create the destination directory.
  356.          */
  357.  
  358.         if (!quietMode) {
  359.         fprintf(stderr, "Installing: %s\n", destFile);
  360.         }
  361.         if (!verifyMode) {
  362.         if (mkdir(destFile, 0777) != 0) {
  363.             fprintf(stderr,
  364.             "Couldn't create destination directory \"%s\": %s.\n",
  365.             destFile, strerror(errno));
  366.             exit(1);
  367.         }
  368.         if (stat(destFile, &destAttr) != 0) {
  369.             fprintf(stderr,
  370.                 "Couldn't stat \"%s\" after creating it: %s.\n",
  371.                 destFile, strerror(errno));
  372.             exit(1);
  373.         }
  374.         if (SetAttributes(destFile, &destAttr, 0) != 0) {
  375.             exit(1);
  376.         }
  377.         }
  378.  
  379.         /*
  380.          * Fall through to the code to put things inside the
  381.          * destination directory.
  382.          */
  383.         goto putInside;
  384.     }
  385.     fprintf(stderr, "Couldn't access \"%s\": %s.\n", destFile,
  386.         strerror(errno));
  387.     exit(1);
  388.     } else {
  389.     if ((destAttr.st_mode & S_IFMT) == S_IFDIR) {
  390.         if (argc == 3) {
  391.         int result;
  392.         if (copyLinks) {
  393.             result = lstat(argv[1], &srcAttr);
  394.         } else {
  395.             result = stat(argv[1], &srcAttr);
  396.         }
  397.         if (result != 0) {
  398.             fprintf(stderr, "Couldn't access \"%s\": %s.\n", argv[1],
  399.                 strerror(errno));
  400.             exit(1);
  401.         }
  402.         if ((srcAttr.st_mode & S_IFMT) == S_IFDIR) {
  403.             /*
  404.              * Update dir1 dir2
  405.              */
  406.             numErrors = Update(argv[1], argv[2]);
  407.             exit(numErrors);
  408.         }
  409.         }
  410.     } else if (argc == 3) {
  411.         /*
  412.          * Update file1 file2
  413.          */
  414.         numErrors = Update(argv[1], argv[2]);
  415.         exit(numErrors);
  416.     } else {
  417.         PrintUsageAndExit(argv);
  418.     }
  419.     }
  420.  
  421.     /*
  422.      * The above cases handled all the replacements.  At this point there
  423.      * are one or more files to be put INSIDE the destination directory:
  424.      *
  425.      * % update src1 ... srcN destDir
  426.      */
  427.  
  428.     putInside:
  429.  
  430.     for (argIndex = 1; argIndex < argc - 1 ; argIndex++) {
  431.     numErrors += UpdateDir("", argv[argIndex], destFile);
  432.     }
  433.  
  434.     if (numErrors == 0) {
  435.     exit(0);
  436.     }
  437.     exit(1);
  438. }
  439.  
  440. /*
  441.  *----------------------------------------------------------------------
  442.  *
  443.  * PrintUsageAndExit --
  444.  *
  445.  *    Print a usage message and exit the process.
  446.  *
  447.  * Results:
  448.  *    Never returns.
  449.  *
  450.  * Side effects:
  451.  *    Stuff gets printed on stderr.
  452.  *
  453.  *----------------------------------------------------------------------
  454.  */
  455.  
  456. static void
  457. PrintUsageAndExit(argv)
  458.     char *argv[];
  459. {
  460.     fprintf(stderr, "Usage: %s srcFile destFile\n", argv[0]);
  461.     fprintf(stderr, "       %s file1 file2 ... destDir\n", argv[0]);
  462.     Opt_PrintUsage(argv[0], optionArray, Opt_Number(optionArray));
  463.     exit(1);
  464. }
  465.  
  466. /*
  467.  *----------------------------------------------------------------------
  468.  *
  469.  * UpdateDir --
  470.  *
  471.  *    Update a file inside a directory.  This creates the name of the
  472.  *    target file from that of the source file and the destination
  473.  *    directory.  Then Update is called to do the actual updating.
  474.  *
  475.  * Results:
  476.  *    The return value is the number of errors encountered.
  477.  *
  478.  * Side effects:
  479.  *    Files get created or replaced.
  480.  *
  481.  *----------------------------------------------------------------------
  482.  */
  483.  
  484. static int
  485. UpdateDir(srcDir, srcFile, destDir)
  486.     char *srcDir;        /* Directory containing srcFile. */
  487.     char *srcFile;        /* Source file name relative to srcDir. */
  488.     char *destDir;        /* Destination directory name. */
  489. {
  490.     register char *charPtr;
  491.     char newDest[MAXPATHLEN];
  492.     char newSrc[MAXPATHLEN];
  493.  
  494.     /*
  495.      * Set up the source file name.  Append the file to the directory name.
  496.      */
  497.     strcpy(newSrc, srcDir);
  498.     if (*srcDir != '\0') {
  499.     strcat(newSrc, "/");
  500.     }
  501.     strcat(newSrc, srcFile);
  502.  
  503.     /*
  504.      * Set up the destination name.  We take the last component of the
  505.      * filename here.  This handles command lines like
  506.      * % update a/b/c dest
  507.      * which will update dest/c from a/b/c.
  508.      * When we are called recusively srcFile only has one component.
  509.      */
  510.     strcpy(newDest, destDir);
  511.     charPtr = strrchr(srcFile, '/');
  512.     if (charPtr == (char *)NULL) {
  513.     strcat(newDest, "/");
  514.     strcat(newDest, srcFile);
  515.     } else {
  516.     strcat(newDest, charPtr);
  517.     }
  518.     return (Update(newSrc, newDest));
  519. }
  520.  
  521. /*
  522.  *----------------------------------------------------------------------
  523.  *
  524.  * Update --
  525.  *
  526.  *    Update a file.  This checks the date stamps on the destination
  527.  *     file and copies the file data if needed, or just updates the
  528.  *    destination file's attributes, or does nothing if the file
  529.  *    is up-to-date.
  530.  *
  531.  * Results:
  532.  *    The return value is the number of errors encountered.
  533.  *
  534.  * Side effects:
  535.  *    None.
  536.  *
  537.  *----------------------------------------------------------------------
  538.  */
  539.  
  540. static int
  541. Update(srcFile, destFile)
  542.     char *srcFile;        /* Name of source file. */
  543.     char *destFile;        /* Name of file that should be made
  544.                  * identical to source file. */
  545. {
  546.     int result;    
  547.     struct stat srcAttr, destAttr, backupAttr;
  548.     char *lastSlash;
  549.     char tmpFileName[MAXPATHLEN];
  550.     static char *typeNames[] = {
  551.     "0",            "010000",        "character special",
  552.     "030000",        "directory",        "050000",
  553.     "block special",    "070000",        "regular file",
  554.     "0110000",        "symbolic link",    "0130000",
  555.     "socket",        "pseudo-device",    "remote link",
  556.     "0170000"
  557.     };
  558.     int backup;
  559.     int    i;
  560.  
  561.     if (prune) {
  562.     lastSlash = strrchr(srcFile, '/');
  563.     if (lastSlash == NULL) {
  564.         lastSlash = srcFile;
  565.     } else {
  566.         lastSlash += 1;
  567.     }
  568.     for (i = 0; i < prune; i++) {
  569.         if (regexec(pruneArray[i].exp, lastSlash)) {
  570.         if (!quietMode) {
  571.             fprintf(stderr, "Pruning:    %s { %s }\n", destFile,
  572.             pruneArray[i].expString);
  573.         }
  574.         return 0;
  575.         }
  576.     }
  577.     }
  578.     if (copyLinks || ignoreLinks) {
  579.     result = lstat(srcFile, &srcAttr);
  580.     } else {
  581.     result = stat(srcFile, &srcAttr);
  582.     }
  583.     if (result != 0) {
  584.     fprintf(stderr, "Couldn't find \"%s\": %s.\n",
  585.         srcFile, strerror(errno));
  586.     return 1;
  587.     }
  588.  
  589.     if (((srcAttr.st_mode & S_IFMT) == S_IFLNK) && ignoreLinks) {
  590.     if (!quietMode) {
  591.         fprintf(stderr, "Ignoring link: %s\n", srcFile);
  592.     }
  593.     return 0;
  594.     }
  595.  
  596.     if ((lstat(destFile, &destAttr) != 0) && (errno = ENOENT)) {
  597.     /*
  598.      * OK to create new file.
  599.      */
  600.     if ((srcAttr.st_mode & S_IFMT) != S_IFDIR) {
  601.         if (!quietMode) {
  602.         fprintf(stderr, "Installing: %s\n", destFile);
  603.         }
  604.         if (verifyMode) {
  605.         return 0;
  606.         }
  607.         return Copy(srcFile, &srcAttr, destFile, 0);
  608.     } else {
  609.         /*
  610.          * Make the target directory and then fall through to
  611.          * the code below which recursively updates it.
  612.          */
  613.         if (!quietMode) {
  614.         fprintf(stderr, "Installing: %s\n", destFile);
  615.         }
  616.         if (!verifyMode) {
  617.         if (mkdir(destFile, (int) (srcAttr.st_mode & 0777)) != 0) {
  618.             fprintf(stderr, "Couldn't create directory \"%s\": %s.\n",
  619.                 destFile, strerror(errno));
  620.             return 1;
  621.         }
  622.         if (SetAttributes(destFile, &srcAttr, 0) != 0) {
  623.             return(1);
  624.         }
  625.         }
  626.         destAttr = srcAttr;
  627.     }
  628.     }
  629.     if ((destAttr.st_mode & S_IFMT) != (srcAttr.st_mode & S_IFMT)) {
  630.     fprintf(stderr,  "Type of \"%s\" (%s) differs from \"%s\" (%s).\n",
  631.         srcFile, typeNames[(srcAttr.st_mode & S_IFMT) >> 12],
  632.         destFile, typeNames[(destAttr.st_mode & S_IFMT) >> 12]);
  633.  
  634.     if (niceMode) {
  635.         return 0;
  636.     }
  637.     /*
  638.      * Don't let the user get confused by failing to copy a link to a link
  639.      * because of type mismatch.  This may easily happen if something
  640.      * is installed as a link and then later the "copyLinks" flag is
  641.      * disabled.
  642.      */
  643.     if (!copyLinks && ((destAttr.st_mode & S_IFMT) == S_IFLNK) &&
  644.         ((srcAttr.st_mode & S_IFMT) == S_IFREG)) {
  645.         if (lstat(srcFile, &srcAttr) == 0 &&
  646.         ((srcAttr.st_mode & S_IFMT) == S_IFLNK)) {
  647.         fprintf(stderr, "\tNote: following a link to a regular file due to \"-l\" option.\n");
  648.         }
  649.     }
  650.     return(1);
  651.     }
  652.     if ((srcAttr.st_mode & S_IFMT) == S_IFDIR) {
  653.     DIR *dirStream;
  654.     DirObject *dirEntryPtr;
  655.     int numErrors = 0;
  656.  
  657.     /*
  658.      * See if we can just rename the directory.
  659.      */
  660.  
  661.     if (move && !verifyMode && !strip) {
  662.         if (rename(srcFile, destFile) == 0) {
  663.         return 0;
  664.         }
  665.     }
  666.  
  667.     /*
  668.      * Recursively update the target directory, which already exists.
  669.      *    Read all names from srcFile directory, and call
  670.      *    UpdateDir(eachName, destFile) to update each one.
  671.      */
  672.  
  673.     dirStream = opendir(srcFile);
  674.     if (dirStream == (DIR *)NULL) {
  675.         fprintf(stderr, "Can't read source directory \"%s\".\n",
  676.                 srcFile);
  677.         return(1);
  678.     }
  679.     dirEntryPtr = readdir(dirStream);
  680.     while (dirEntryPtr != (DirObject *)NULL) {
  681.         if ((dirEntryPtr->d_namlen == 1 &&
  682.          dirEntryPtr->d_name[0] == '.') ||
  683.         (dirEntryPtr->d_namlen == 2 &&
  684.          dirEntryPtr->d_name[0] == '.' &&
  685.          dirEntryPtr->d_name[1] == '.')) {
  686.         /* Don't do "." or ".." */ ;
  687.         } else {
  688.         numErrors += UpdateDir(srcFile, dirEntryPtr->d_name,
  689.             destFile);
  690.         }
  691.         dirEntryPtr = readdir(dirStream);
  692.     }
  693.     closedir(dirStream);
  694.  
  695.     /*
  696.      * If moving, must delete source directory.
  697.      */
  698.  
  699.     if (move && !verifyMode && (rmdir(srcFile) != 0)) {
  700.         fprintf(stderr,
  701.             "Couldn't remove directory \"%s\" during move: %s\n",
  702.             srcFile, strerror(errno));
  703.         numErrors++;
  704.     }
  705.     return(numErrors);
  706.      }
  707.      if (force || (destAttr.st_mtime < srcAttr.st_mtime)) {
  708.     /*
  709.      * Target file has to be updated.
  710.      */
  711.  
  712.     if (!quietMode) {
  713.         fprintf(stderr, "Updating: %s\n", destFile);
  714.     }
  715.     if (verifyMode) {
  716.         return 0;
  717.     }
  718.  
  719.     /*
  720.      * If no backup directory, then rename the file, copy a new
  721.      * version in, and remove the old renamed target.
  722.      */
  723.  
  724.     if (backupDir == (char *) 0) {
  725.         sprintf(tmpFileName, "%sXXX", destFile);
  726.  
  727.         if (rename(destFile, tmpFileName) != 0) {
  728.         fprintf(stderr, "Couldn't rename \"%s\" to \"%s\": %s.\n",
  729.             destFile, tmpFileName, strerror(errno));
  730.         return(1);
  731.         }
  732.         if (Copy(srcFile, &srcAttr, destFile, 0) == 0) {
  733.         if (unlink(tmpFileName) != 0) {
  734.             fprintf(stderr,
  735.                 "Couldn't remove renamed old version \"%s\": %s.\n",
  736.                 tmpFileName, strerror(errno));
  737.             return 1;
  738.         }
  739.         } else {
  740.         if (rename(tmpFileName, destFile) != 0) {
  741.             fprintf(stderr,
  742.             "Couldn't restore original \"%s\":  see \"%s\".\n",
  743.             destFile, tmpFileName);
  744.         }
  745.         return 1;
  746.         }
  747.         return 0;
  748.     }
  749.  
  750.     /*
  751.      * There's a backup directory.  Copy the file to it, then
  752.      * copy in new version.
  753.      */
  754.  
  755.     lastSlash = strrchr(destFile, '/');
  756.     if (lastSlash == NULL) {
  757.         lastSlash = destFile;
  758.     } else {
  759.         lastSlash += 1;
  760.     }
  761.     sprintf(tmpFileName, "%s/%s", backupDir, lastSlash);
  762.  
  763.     backup = 1;
  764.     if (backupAge > 0 && stat(tmpFileName, &backupAttr) == 0) {
  765.  
  766.         if (destAttr.st_mtime > backupAge) {
  767.         if (!quietMode) {
  768.             fprintf(stderr,
  769.                 "\tWarning: target too recent, so not overwriting backup copy.\n");
  770.         }
  771.         backup = 0;
  772.         }
  773.     }
  774.     if (backup && Copy(destFile, &destAttr, tmpFileName, 1) != 0) {
  775.         fprintf(stderr,
  776.             "Couldn't copy \"%s\" to backup dir \"%s\".\n",
  777.             destFile, backupDir);
  778.         return 1;
  779.     }
  780.     unlink(destFile);
  781.     return Copy(srcFile, &srcAttr, destFile, 0);
  782.     }
  783.     return 0;
  784. }
  785.  
  786. /*
  787.  *----------------------------------------------------------------------
  788.  *
  789.  * Copy --
  790.  *
  791.  *    Copy a file.  For regular files this copies the file data.
  792.  *    For other special files a new file of the same type as
  793.  *    the original is created.
  794.  *
  795.  * Results:
  796.  *    0 is returned if all went well, and 1 is returned if an
  797.  *    error occurred.
  798.  *
  799.  * Side effects:
  800.  *    srcFile gets copied to destFile.
  801.  *
  802.  *----------------------------------------------------------------------
  803.  */
  804.  
  805. static int
  806. Copy(srcFile, srcAttrPtr, destFile, backup)
  807.     char *srcFile;            /* Name of source file. */
  808.     struct stat *srcAttrPtr;        /* Attributes of source file (used to
  809.                      * save stat calls). */
  810.     char *destFile;            /* Name of destination file. */
  811.     int backup;                /* Non-zero means this is a backup
  812.                      * copy being made;  as a result,
  813.                      * attribute changes are handled
  814.                      * differently. */
  815. {
  816. #define BUFSIZE 8192
  817.     static char buffer[BUFSIZE];
  818.  
  819.     /*
  820.      * See if we can just rename the file.
  821.      */
  822.     if (move && (realUid == srcAttrPtr->st_uid) && !strip) {
  823.     if (rename(srcFile, destFile) == 0) {
  824.         return 0;
  825.     }
  826.     }
  827.  
  828.     switch (srcAttrPtr->st_mode & S_IFMT) {
  829.     case S_IFREG: {
  830.         int result = 0;
  831.         int amountRead, bytesCopied;
  832.         int maxSize = INT_MAX;
  833.         int srcID, destID;
  834.         int stripping = 0;
  835.  
  836.         srcID = open(srcFile, O_RDONLY, 0); 
  837.         if (srcID < 0) {
  838.         fprintf(stderr, "Couldn't open \"%s\": %s.\n",
  839.             srcFile, strerror(errno));
  840.         return 1;
  841.         }
  842.         (void) unlink(destFile);
  843.         destID = open(destFile, O_WRONLY|O_CREAT|O_TRUNC,
  844.             srcAttrPtr->st_mode & 0777);
  845.         if (destID < 0) {
  846.         char pathname[MAXPATHLEN];
  847.         char *s;
  848.         int realErrno;
  849.  
  850.         /*
  851.          * Some of the subdirectories in the path may
  852.          * not exist.  Try and create them, and if
  853.          * successful, try to open the file again.  Make sure
  854.          * that if we don't successfully create subdirectories and
  855.          * then redo the open, we return the errno corresponding
  856.          * to the open, not the failed mkdir.
  857.          */
  858.  
  859.         realErrno = errno;
  860.         strcpy(pathname, destFile);
  861.         if ((s = strrchr(pathname, '/')) != NULL) {
  862.             *s = '\0';
  863.             if (CreateDirectory(pathname) == 0) {
  864.             destID = open(destFile, O_WRONLY|O_CREAT|O_TRUNC,
  865.                 srcAttrPtr->st_mode & 0777);
  866.             } else {
  867.             errno = realErrno;
  868.             }
  869.         }
  870.         if (destID < 0) {
  871.             fprintf(stderr, "Couldn't create \"%s\": %s.\n",
  872.             destFile, strerror(errno));
  873.             return 1;
  874.         }
  875.         }
  876. #if (!defined(ds3100) && !defined(__mips) && !defined(hpux))
  877.         if (strip) {
  878.         struct exec hdr;
  879.         if ((read(srcID, (char *) &hdr, sizeof(hdr)) != sizeof(hdr))
  880.             || N_BADMAG(hdr)) {
  881.             fprintf(stderr,
  882.                 "\"%s\" isn't a binary executable;  can't strip.\n",
  883.                 srcFile);
  884.         } else {
  885.             maxSize = N_TXTOFF(hdr) + hdr.a_text + hdr.a_data;
  886.             stripping = 1;
  887.         }
  888.         lseek(srcID, (long) 0, L_SET);
  889.         }
  890. #endif
  891.  
  892.         for (bytesCopied = 0; bytesCopied < maxSize;
  893.             bytesCopied += amountRead) {
  894.         int wanted;
  895.  
  896.         wanted = maxSize - bytesCopied;
  897.         if (wanted > BUFSIZE) {
  898.             wanted = BUFSIZE;
  899.         }
  900.         amountRead = read(srcID, buffer, wanted);
  901.         if (amountRead < 0) {
  902.             fprintf(stderr, "Read error on \"%s\": %s.\n",
  903.                 srcFile, strerror(errno));
  904.             result = 1;
  905.             break;
  906.         }
  907.         if (amountRead == 0) {
  908.             break;
  909.         }
  910.  
  911. #if (!defined(ds3100) && !defined(__mips) && !defined(hpux))
  912.         /*
  913.          * If we're stripping, clear out the symbol table size
  914.          * in the file's header.
  915.          */
  916.  
  917.         if ((bytesCopied == 0) && stripping) {
  918.             struct exec *execPtr = (struct exec *) buffer;
  919.  
  920.             execPtr->a_syms = 0;
  921.             execPtr->a_trsize = 0;
  922.             execPtr->a_drsize = 0;
  923.         }
  924. #endif
  925.         if (write(destID, buffer, amountRead) != amountRead) {
  926.             fprintf(stderr, "Write error on \"%s\": %s.\n",
  927.                 destFile, strerror(errno));
  928.             result = 1;
  929.             break;
  930.         }
  931.         }
  932.         close(srcID);
  933.         close(destID);
  934.         if (result != 0) {
  935.         return result;
  936.         }
  937.         break;
  938.     }
  939.     case S_IFDIR: {
  940.         fprintf(stderr, "Internal error:  Copy called with directory.\n");
  941.         return 1;
  942.     }
  943.     case S_IFLNK:
  944. #ifdef sprite
  945.     case S_IFRLNK:
  946. #endif
  947.         {
  948.         char targetName[MAXPATHLEN];
  949.         int length;
  950.         ReturnStatus    status;
  951.  
  952.         length = readlink(srcFile, targetName, MAXPATHLEN);
  953.         if (length < 0) {
  954.         fprintf(stderr, "Couldn't read value of link \"%s\": %s.\n",
  955.             srcFile, strerror(errno));
  956.         return 1;
  957.         }
  958.         targetName[length] = 0;
  959.         status = MAKELINK(targetName, destFile, 
  960.                   (srcAttrPtr->st_mode & S_IFMT));
  961.         if (status != SUCCESS) {
  962.         char pathname[MAXPATHLEN];
  963.         char *s;
  964.         int realStatus;
  965.  
  966.         /*
  967.          * Some of the subdirectories in the path may
  968.          * not exist.  Try and create them, and if
  969.          * successful, try to open the file again.  Make sure
  970.          * that if we don't successfully create subdirectories and
  971.          * then redo the open, we return the errno corresponding
  972.          * to the open, not the failed mkdir.
  973.          */
  974.  
  975.         realStatus = status;
  976.         strcpy(pathname, destFile);
  977.         if ((s = strrchr(pathname, '/')) != NULL) {
  978.             *s = '\0';
  979.             if (CreateDirectory(pathname) == 0) {
  980.             status = MAKELINK(targetName, destFile, 
  981.                 (srcAttrPtr->st_mode & S_IFMT));
  982.             } else {
  983.             status = realStatus;
  984.             }
  985.         }
  986.         if (status != SUCCESS) {
  987.             fprintf(stderr,
  988.                 "Couldn't create link at \"%s\": %s.\n",
  989.                 destFile, MAKEMSG(status));
  990.             return 1;
  991.         }
  992.         }
  993.         break;
  994.     }
  995.     case S_IFBLK:
  996.     case S_IFCHR: {
  997.         ReturnStatus    status;
  998. #ifdef sprite
  999.         Fs_Attributes     attrs;
  1000.         Fs_Device        device;
  1001. #else
  1002.         struct stat attrs;
  1003. #endif
  1004.  
  1005.         status = GETATTRS(srcFile, &attrs);
  1006.         if (status != SUCCESS) {
  1007.         fprintf(stderr, "Unable to get attributes of \"%s\": %s\n",
  1008.             srcFile, MAKEMSG(status));
  1009.         return 1;
  1010.         }
  1011. #ifdef sprite
  1012.         device.type = attrs.devType;
  1013.         device.unit = attrs.devUnit;
  1014.         device.serverID = attrs.devServerID;
  1015.         device.data = (ClientData) 0;
  1016.         status = Fs_MakeDevice(destFile, &device, 
  1017.                    srcAttrPtr->st_mode & 0777);
  1018. #else
  1019.         status = mknod(destFile, srcAttrPtr->st_mode,
  1020.                srcAttrPtr->st_dev);
  1021. #endif
  1022.         if (status != SUCCESS) {
  1023.         fprintf(stderr, "Unable to create device \"%s\": %s\n",
  1024.             destFile, MAKEMSG(status));
  1025.         return 1;
  1026.         }
  1027.         break;
  1028.     }
  1029.     case S_IFIFO: {
  1030.         fprintf(stderr, "\"%s\" is a fifo; don't know how to update.\n",
  1031.             srcFile);
  1032.         return 1;
  1033.     }
  1034. #ifdef sprite
  1035.     case S_IFPDEV: {
  1036.         fprintf(stderr,
  1037.             "\"%s\" is a pseudo-device; don't know how to update.\n",
  1038.             srcFile);
  1039.         return 1;
  1040.     }
  1041. #endif
  1042.     default: {
  1043.         fprintf(stderr,
  1044.             "\"%s\" has type 0x%x; don't know how to update.\n",
  1045.             srcFile, srcAttrPtr->st_mode & S_IFMT);
  1046.         return 1;
  1047.     }
  1048.     }
  1049.     if (SetAttributes(destFile, srcAttrPtr, backup) != 0) {
  1050.     return 1;
  1051.     }
  1052.     if (move) {
  1053.     if (unlink(srcFile) != 0) {
  1054.         fprintf(stderr,
  1055.             "Couldn't remove source file \"%s\" during move: %s.\n",
  1056.             srcFile, strerror(errno));
  1057.         return 1;
  1058.     }
  1059.     }
  1060.     return 0;
  1061. }
  1062.  
  1063. /*
  1064.  *----------------------------------------------------------------------
  1065.  *
  1066.  * CheckGroup --
  1067.  *
  1068.  *    Verify that a user is in a particular group.  This is called to
  1069.  *    check against the wrong users attempting to use update to
  1070.  *    change file ownership.
  1071.  *
  1072.  * Results:
  1073.  *    Returns if the user is ok, otherwise this exits.
  1074.  *
  1075.  * Side effects:
  1076.  *    Suicide if the user isn't in the group.
  1077.  *
  1078.  *----------------------------------------------------------------------
  1079.  */
  1080.  
  1081. static void
  1082. CheckGroup(groupName, userName)
  1083.     char *groupName;        /* group name */
  1084.     char *userName;        /* user name */
  1085. {
  1086.     register struct group *grPtr;
  1087.     int i;
  1088.  
  1089.     grPtr = getgrnam(groupName);
  1090.     if (grPtr == (struct group *) 0) {
  1091.     fprintf(stderr, "Couldn't find \"%s\" group to check owner change.\n",
  1092.         groupName);
  1093.     exit(1);
  1094.     }
  1095.     for (i = 0; ; i++) {
  1096.     if (grPtr->gr_mem[i] == NULL) {
  1097.         fprintf(stderr,
  1098.             "Can't change owners: you're not in the \"%s\" group.\n",
  1099.             groupName);
  1100.         exit(1);
  1101.     }
  1102.     if (strcmp(grPtr->gr_mem[i], userName) == 0) {
  1103.         break;
  1104.     }
  1105.     }
  1106.     endgrent();
  1107.     return;
  1108. }
  1109.  
  1110. /*
  1111.  *----------------------------------------------------------------------
  1112.  *
  1113.  * SetAttributes --
  1114.  *
  1115.  *    Set attributes of target file, taking into account command line
  1116.  *    options that specify group and new mode.
  1117.  *
  1118.  * Results:
  1119.  *    Returns 0 if all went well, 1 if there was an error.
  1120.  *
  1121.  * Side effects:
  1122.  *    Times, protection, and group may get changed for fileName.
  1123.  *
  1124.  *----------------------------------------------------------------------
  1125.  */
  1126.  
  1127. static int
  1128. SetAttributes(fileName, attrPtr, backup)
  1129.     char *fileName;            /* Name of file to change. */
  1130.     struct stat *attrPtr;        /* Attributes of source file, which
  1131.                      * are used (along with command-line
  1132.                      * options) to set fileName. */
  1133.     int backup;                /* Non-zero means that this is a
  1134.                      * backup copy whose attributes are
  1135.                      * being set;  ignore command-line
  1136.                      * options and just copy relevant
  1137.                      * attributes from the source. */
  1138.  
  1139. {
  1140. #ifdef SYSV
  1141.     struct utimbuf times;
  1142. #else
  1143.     struct timeval times[2];
  1144. #endif
  1145.  
  1146.     /*
  1147.      * Preserve ownership if requested (or if this is a backup copy).
  1148.      */
  1149.  
  1150.     if (preserveOwnership && !backup) {
  1151.     if (chown(fileName, attrPtr->st_uid, attrPtr->st_gid) != 0) {
  1152.         fprintf(stderr, "Couldn't set owner for \"%s\": %s.\n",
  1153.             fileName, strerror(errno));
  1154.         return 1;
  1155.     }
  1156.     }
  1157.  
  1158.     /*
  1159.      * Set group if a specific one was requested.
  1160.      */
  1161.  
  1162.     if ((newGroup >= 0) && !backup) {
  1163.     if (chown(fileName, -1, newGroup) != 0) {
  1164.         fprintf(stderr, "Couldn't set group id for \"%s\": %s.\n",
  1165.             fileName, strerror(errno));
  1166.         return 1;
  1167.     }
  1168.     }
  1169.  
  1170.     /*
  1171.      * Don't set permissions or times for symbolic links, since they'll
  1172.      * end up affecting the target of the link.
  1173.      */
  1174.  
  1175.     if ((attrPtr->st_mode & S_IFMT) == S_IFLNK) {
  1176.     return 0;
  1177.     }
  1178.  
  1179.     /*
  1180.      * Set permissions (but only if the source isn't a symbolic link;  if
  1181.      * it's a link, then we'd be setting the permissions of the link's
  1182.      * target).
  1183.      */
  1184.  
  1185.     if ((newMode >= 0) && !backup) {
  1186.     if (chmod(fileName, newMode) != 0) {
  1187.         fprintf(stderr,
  1188.             "Couldn't set permissions of \"%s\" to 0%o: %s.\n",
  1189.             fileName, newMode, strerror(errno));
  1190.         return 1;
  1191.     }
  1192.     } else {
  1193.     /* 
  1194.      * Set the file permissions.  When the file was created the
  1195.      * permissions were modified by umask, so we have to set them
  1196.      * here.
  1197.      */
  1198.     if (chmod(fileName, attrPtr->st_mode & 0777) != 0) {
  1199.         fprintf(stderr,
  1200.             "Couldn't set permissions for \"%s\": %s.\n",
  1201.             fileName, strerror(errno));
  1202.         return 1;
  1203.     }
  1204.     /*
  1205.      * Preserve the set-user-id bit if a new mode wasn't given, and if
  1206.      * the set-user-id bit was set in the source file, and if the file's
  1207.      * new owner is the same as its previous owner.
  1208.      */
  1209.     
  1210.     if ((attrPtr->st_mode & (S_ISUID)) &&
  1211.         (preserveOwnership || (realUid == attrPtr->st_uid))) {
  1212.         if (chmod(fileName, attrPtr->st_mode & 04777) != 0) {
  1213.         fprintf(stderr,
  1214.             "Couldn't set set-user-id for \"%s\": %s.\n",
  1215.             fileName, strerror(errno));
  1216.         return 1;
  1217.         }
  1218.     }
  1219.     }
  1220.  
  1221.     /*
  1222.      * Set times.
  1223.      */
  1224.  
  1225.     if ((setTimes) || backup) {
  1226. #ifdef SYSV
  1227.     times.actime = attrPtr->st_atime;
  1228.     times.modtime = attrPtr->st_mtime;
  1229.     if (utime(fileName, ×) != 0) {
  1230. #else
  1231.     times[0].tv_usec = times[1].tv_usec = 0;
  1232.     times[0].tv_sec = attrPtr->st_atime;
  1233.     times[1].tv_sec = attrPtr->st_mtime;
  1234.     if (utimes(fileName, times) != 0) {        
  1235. #endif
  1236.         fprintf(stderr, "Couldn't set times of \"%s\": %s.\n",
  1237.             fileName, strerror(errno));
  1238.         return 1;
  1239.     }
  1240.     }
  1241.  
  1242. #ifdef sprite
  1243.     /*
  1244.      * Set the advisory file type if it's set in the source file.
  1245.      */
  1246.     if (attrPtr->st_userType != S_TYPE_UNDEFINED) {
  1247.     if (setfiletype(fileName, attrPtr->st_userType) != 0) {
  1248.         fprintf(stderr, "Couldn't set type of \"%s\": %s.\n",
  1249.             fileName, strerror(errno));
  1250.     }
  1251.     }
  1252. #endif
  1253.  
  1254.     return 0;
  1255. }
  1256.  
  1257. static int
  1258. CreateDirectory(dir)
  1259.     char *dir;
  1260. {
  1261.     char pathname[MAXPATHLEN];
  1262.     char *s;
  1263.  
  1264.     /*
  1265.      * Try to create the directory
  1266.      */
  1267.     if (mkdir(dir, 0777) == 0) {
  1268.     return 0;
  1269.     }
  1270.  
  1271.     /*
  1272.      * Couldn't create the directory.
  1273.      * Try to create the parent directory, and then try again.
  1274.      */
  1275.     strcpy(pathname, dir);
  1276.     if ((s = strrchr(pathname, '/')) == NULL) {
  1277.     return 1;
  1278.     }
  1279.     *s = '\0';
  1280.     if (CreateDirectory(pathname) == 0) {
  1281.     return mkdir(dir, 0777);
  1282.     }
  1283.     return 1;
  1284. }
  1285.  
  1286. /*
  1287.  *----------------------------------------------------------------------
  1288.  *
  1289.  * PruneOpt --
  1290.  *
  1291.  *    Process a "-p" option.  Take the regular expression that follows
  1292.  *    and put it in the array of trees to prune..
  1293.  *
  1294.  * Results:
  1295.  *    1 (-p option requires an argument).
  1296.  *
  1297.  * Side effects:
  1298.  *    The argument to the option is put in the regular expression array..
  1299.  *
  1300.  *----------------------------------------------------------------------
  1301.  */
  1302.  
  1303. static int
  1304. PruneOpt(optionPtr, exprString)
  1305.     char    *optionPtr;     /* Current option. */
  1306.     char    *exprString;    /* The regular expression. */
  1307. {
  1308.     regexp    *expPtr;
  1309.  
  1310.     if ((exprString == NULL) || (*exprString == '-')) {
  1311.     fprintf(stderr, "Warning: \"-%s\" option needs an argument\n", 
  1312.         optionPtr);
  1313.     return 1;
  1314.     }
  1315.     expPtr = regcomp(exprString);
  1316.     if (expPtr == NULL) {
  1317.     fprintf(stderr, "Warning: \"%s\" is not a regular expression.\n", 
  1318.         exprString);
  1319.     return 1;
  1320.     }
  1321.     if (prune >= MAX_PRUNE) {
  1322.     fprintf(stderr, "Warning: too many prune options.\n");
  1323.     return 1;
  1324.     }
  1325.     pruneArray[prune].exp = expPtr;
  1326.     pruneArray[prune].expString = exprString;
  1327.     prune++;
  1328.     return 1;
  1329. }
  1330.  
  1331. /*
  1332.  *----------------------------------------------------------------------
  1333.  *
  1334.  * regerror --
  1335.  *
  1336.  *    This routine is called by the regular expression library when
  1337.  *    something goes wrong.
  1338.  *
  1339.  * Results:
  1340.  *    None.
  1341.  *
  1342.  * Side effects:
  1343.  *    Stuff is printed out.
  1344.  *
  1345.  *----------------------------------------------------------------------
  1346.  */
  1347.  
  1348. void
  1349. regerror(msg)
  1350.     char    *msg;    /* The error message. */
  1351. {
  1352.     fprintf(stderr, "Error in regular expression library: %s\n", msg);
  1353.     exit(1);
  1354. }
  1355.  
  1356.  
  1357. #ifdef NEEDSTRERROR
  1358.  
  1359. /*
  1360.  *----------------------------------------------------------------------
  1361.  *
  1362.  * strerror --
  1363.  *
  1364.  *      Simplistic message generator for systems that
  1365.  *    don't have the real thing.
  1366.  *
  1367.  * Results:
  1368.  *    None.
  1369.  *
  1370.  * Side effects:
  1371.  *    None.
  1372.  *
  1373.  *----------------------------------------------------------------------
  1374.  */
  1375.  
  1376. char *
  1377. strerror(error)
  1378.     int error;
  1379. {
  1380.     extern char *sys_errlist[];
  1381.     extern int sys_nerr;
  1382.     static char badMsg[100];
  1383.  
  1384.     if (error > sys_nerr) {
  1385.     sprintf(badMsg, "Unknown errno value: %d\n", error);
  1386.     return badMsg;
  1387.     } else {
  1388.     return sys_errlist[error];
  1389.     }
  1390. }
  1391.  
  1392. #endif
  1393.